/*
 * Use this code at your own risk.  This code has no warranty
 * and the author is not responsible for any damage, harm,
 * or dissatisfaction from using or running the code.  Make 
 * sure that you understand the code before running it.
 * 
 * You may re-use this code in whole or in part, but please
 * give the author credit in your code comments.
 * 
 * Mike Hodnick
 * www.kindohm.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using _3DTools;
using WPF3DTutorial.Model;
using InteractiveViewport3DSample;

namespace WPF3DTutorial
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    public partial class Window1 : System.Windows.Window
    {

        public Window1()
        {
            InitializeComponent();

            this.visualizers.Add(new CubeVisualizer(this));
            this.visualizers.Add(new SquarePyramidVisualizer(this));
            this.visualizers.Add(new TetrahedronVisualizer(this));

            ComboBoxItem item = new ComboBoxItem();
            item.Content = "Cube";
            this.cbModels.Items.Add(item);

            item = new ComboBoxItem();
            item.Content = "Square Pyramid";
            this.cbModels.Items.Add(item);

            item = new ComboBoxItem();
            item.Content = "Tetrahedron";
            this.cbModels.Items.Add(item);

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            this.MouseUp += new MouseButtonEventHandler(Window1_MouseUp);
        }

        private Stack<Point3D> clicked = new Stack<Point3D>();

        public void Window1_MouseUp(object sender, EventArgs e) {
            
        }

        private void simpleButtonClick(object sender, RoutedEventArgs e)
        {
            ClearViewport();
            SetCamera();

            MeshGeometry3D triangleMesh = new MeshGeometry3D();
            Point3D point0 = new Point3D(0, 0, 0);
            Point3D point1 = new Point3D(5, 0, 0);
            Point3D point2 = new Point3D(0, 0, 5);
            triangleMesh.Positions.Add(point0);
            triangleMesh.Positions.Add(point1);
            triangleMesh.Positions.Add(point2);
            triangleMesh.TriangleIndices.Add(0);
            triangleMesh.TriangleIndices.Add(2);
            triangleMesh.TriangleIndices.Add(1);
            Vector3D normal = new Vector3D(0, 1, 0);
            triangleMesh.Normals.Add(normal);
            triangleMesh.Normals.Add(normal);
            triangleMesh.Normals.Add(normal);
            Material material = new DiffuseMaterial(
                new SolidColorBrush(Colors.DarkKhaki));
            GeometryModel3D triangleModel = new GeometryModel3D(
                triangleMesh, material);
            ModelVisual3D model = new ModelVisual3D();
            model.Content = triangleModel;
            this.mainViewport.Children.Add(model);
        }

        private void cubeButtonClick(object sender, RoutedEventArgs e)
        {
            ClearViewport();
            SetCamera();
            Model3DGroup cube = GetCube();

            ModelVisual3D model = new ModelVisual3D();
            model.Content = cube;
            this.mainViewport.Children.Add(model);
        }

        private Model3DGroup GetCube() {

            Model3DGroup cube = new Model3DGroup();
            Point3D p0 = new Point3D(0, 0, 0);
            Point3D p1 = new Point3D(5, 0, 0);
            Point3D p2 = new Point3D(5, 0, 5);
            Point3D p3 = new Point3D(0, 0, 5);
            Point3D p4 = new Point3D(0, 5, 0);
            Point3D p5 = new Point3D(5, 5, 0);
            Point3D p6 = new Point3D(5, 5, 5);
            Point3D p7 = new Point3D(0, 5, 5);

            //front side triangles
            cube.Children.Add(CreateTriangleModel(p3, p2, p6));
            cube.Children.Add(CreateTriangleModel(p3, p6, p7));
            //right side triangles
            cube.Children.Add(CreateTriangleModel(p2, p1, p5));
            cube.Children.Add(CreateTriangleModel(p2, p5, p6));
            //back side triangles
            cube.Children.Add(CreateTriangleModel(p1, p0, p4));
            cube.Children.Add(CreateTriangleModel(p1, p4, p5));
            //left side triangles
            cube.Children.Add(CreateTriangleModel(p0, p3, p7));
            cube.Children.Add(CreateTriangleModel(p0, p7, p4));
            //top side triangles
            cube.Children.Add(CreateTriangleModel(p7, p6, p5));
            cube.Children.Add(CreateTriangleModel(p7, p5, p4));
            //bottom side triangles
            cube.Children.Add(CreateTriangleModel(p2, p3, p0));
            cube.Children.Add(CreateTriangleModel(p2, p0, p1));
            return cube;
        }
        
        public Model3DGroup CreateTriangleModel(
            Point3D p0, Point3D p1, Point3D p2) {
            return this.CreateTriangleModel(p0, p1, p2, Colors.DarkKhaki);
        }

        public Model3DGroup CreateTriangleModel(
            Point3D p0, Point3D p1, Point3D p2, Color color)
        {
            MeshGeometry3D mesh = new MeshGeometry3D();
            mesh.Positions.Add(p0);
            mesh.Positions.Add(p1);
            mesh.Positions.Add(p2);
            mesh.TriangleIndices.Add(0);
            mesh.TriangleIndices.Add(1);
            mesh.TriangleIndices.Add(2);
            Vector3D normal = CalculateNormal(p0, p1, p2);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            mesh.Normals.Add(normal);
            Material material = new DiffuseMaterial(
                new SolidColorBrush(color));
            GeometryModel3D model = new GeometryModel3D(
                mesh, material);
            Model3DGroup group = new Model3DGroup();
            group.Children.Add(model);

            if (normalsCheckBox.IsChecked == true)
                group.Children.Add(BuildNormals(p0, p1, p2, normal));

            if (wireframeCheckBox.IsChecked == true)
            {
                ScreenSpaceLines3D wireframe = new ScreenSpaceLines3D();
                wireframe.Points.Add(p0);
                wireframe.Points.Add(p1);
                wireframe.Points.Add(p2);
                wireframe.Points.Add(p0);
                wireframe.Color = Colors.LightBlue;
                wireframe.Thickness = 2;

                this.mainViewport.Children.Add(wireframe);
            }

            return group;
        }

        public Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
        {
            Vector3D v0 = new Vector3D(
                p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
            Vector3D v1 = new Vector3D(
                p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
            return Vector3D.CrossProduct(v0, v1);
        }

        private void ClearViewport()
        {
            ModelVisual3D m;
            for (int i = mainViewport.Children.Count - 1; i >= 0; i--)
            {
                m = (ModelVisual3D)mainViewport.Children[i];
                if (m.Content is DirectionalLight == false)
                    mainViewport.Children.Remove(m);
            }
        }

        private void SetCamera()
        {
            PerspectiveCamera camera = (PerspectiveCamera)mainViewport.Camera;
            Point3D position = new Point3D(12, 12, 12);
            Vector3D lookDirection = new Vector3D(-9,-8,-10);
            
            camera.Position = position;
            camera.LookDirection = lookDirection;

        }

        public static Rect Get2DBoundingBox(Viewport3D vp) {
            bool bOK;

            Viewport3DVisual vpv =
                VisualTreeHelper.GetParent(
                    vp.Children[0]) as Viewport3DVisual;

            Matrix3D m =
                MathUtils.TryWorldToViewportTransform(vpv, out bOK);

            bool bFirst = true;
            Rect r = new Rect();

            foreach (Visual3D v3d in vp.Children) {
                if (v3d is ModelVisual3D) {
                    ModelVisual3D mv3d = (ModelVisual3D)v3d;
                    if (mv3d.Content is GeometryModel3D) {
                        GeometryModel3D gm3d =
                            (GeometryModel3D)mv3d.Content;

                        if (gm3d.Geometry is MeshGeometry3D) {
                            MeshGeometry3D mg3d =
                                (MeshGeometry3D)gm3d.Geometry;

                            foreach (Point3D p3d in mg3d.Positions) {
                                Point3D pb = m.Transform(p3d);
                                Point p2d = new Point(pb.X, pb.Y);
                                if (bFirst) {
                                    r = new Rect(p2d, new Size(1, 1));
                                    bFirst = false;
                                } else {
                                    r.Union(p2d);
                                }
                            }
                        }
                    }
                }
            }

            return r;
        }
        private bool TooBig() {
            Rect r = Get2DBoundingBox(this.mainViewport);

            if (r.Left < 0) {
                return true;
            }
            if (r.Right > this.mainViewport.ActualWidth) {
                return true;
            }
            if (r.Top < 0) {
                return true;
            }
            if (r.Bottom > this.mainViewport.ActualHeight) {
                return true;
            }
            return false;
        }

        void OnClick_Fit(object sender, RoutedEventArgs args) {
            if (TooBig()) {
                while (TooBig()) {
                    this.ZoomSlider.Value -= 0.1;
                }
                while (!TooBig()) {
                    ZoomSlider.Value += 0.01;
                }
                ZoomSlider.Value -= 0.01;
            } else {
                while (!TooBig()) {
                    ZoomSlider.Value += 0.1;
                }
                while (TooBig()) {
                    ZoomSlider.Value -= 0.01;
                }
            }
        }

        private Model3DGroup BuildNormals(
    Point3D p0,
    Point3D p1,
    Point3D p2,
    Vector3D normal)
        {
            Model3DGroup normalGroup = new Model3DGroup();
            Point3D p;
            ScreenSpaceLines3D normal0Wire = new ScreenSpaceLines3D();
            ScreenSpaceLines3D normal1Wire = new ScreenSpaceLines3D();
            ScreenSpaceLines3D normal2Wire = new ScreenSpaceLines3D();
            Color c = Colors.Blue;
            int width = 1;
            normal0Wire.Thickness = width;
            normal0Wire.Color = c;
            normal1Wire.Thickness = width;
            normal1Wire.Color = c;
            normal2Wire.Thickness = width;
            normal2Wire.Color = c;
            double num = 1;
            double mult = .01;
            double denom = mult * Convert.ToDouble(normalSizeTextBox.Text);
            double factor = num / denom;
            p = Vector3D.Add(Vector3D.Divide(normal, factor), p0);
            normal0Wire.Points.Add(p0);
            normal0Wire.Points.Add(p);
            p = Vector3D.Add(Vector3D.Divide(normal, factor), p1);
            normal1Wire.Points.Add(p1);
            normal1Wire.Points.Add(p);
            p = Vector3D.Add(Vector3D.Divide(normal, factor), p2);
            normal2Wire.Points.Add(p2);
            normal2Wire.Points.Add(p);

            //Normal wires are not models, so we can't
            //add them to the normal group.  Just add them
            //to the viewport for now...
            this.mainViewport.Children.Add(normal0Wire);
            this.mainViewport.Children.Add(normal1Wire);
            this.mainViewport.Children.Add(normal2Wire);

            return normalGroup;
        }

        private Point3D[] GetRandomTopographyPoints()
        {
            //create a 10x10 topography.
            Point3D[] points = new Point3D[100];
            Random r = new Random();
            double y;
            double denom = 1000;
            int count = 0;
            for (int z = 0; z < 10; z++)
            {
                for (int x = 0; x < 10; x++)
                {
                    System.Threading.Thread.Sleep(1);
                    y = Convert.ToDouble(r.Next(1, 999)) / denom;
                    points[count] = new Point3D(x, y, z);
                    count += 1;
                }
            }
            return points;
        }

        private void topographyButtonClick(object sender, RoutedEventArgs e)
        {
            ClearViewport();
            SetCamera();
            Model3DGroup topography = new Model3DGroup();
            Point3D[] points = GetRandomTopographyPoints();
            for (int z = 0; z <= 80; z = z + 10)
            {
                for (int x = 0; x < 9; x++)
                {
                    topography.Children.Add(
                        CreateTriangleModel(
                                points[x + z],
                                points[x + z + 10],
                                points[x + z + 1])
                    );
                    topography.Children.Add(
                        CreateTriangleModel(
                                points[x + z + 1],
                                points[x + z + 10],
                                points[x + z + 11])
                    );
                }
            }
            ModelVisual3D model = new ModelVisual3D();
            model.Content = topography;
            this.mainViewport.Children.Add(model);
        }

        private void btnPyramid_Click(object sender, RoutedEventArgs e) {
        }


        private void CircleButton_Click(object sender, RoutedEventArgs e) {
            ClearViewport();
            SetCamera();

            ModelVisual3D model = CreateTextLabel3D(
                "Hello", new SolidColorBrush(Colors.Tan), 
                true, 10, new Point3D(2.5, -2.5, 7),
                new Vector3D(), new Vector3D());
            this.mainViewport.Children.Add(model);
        }

        private void Rectangle_Click(object sender, RoutedEventArgs e) {
            Visualize();
        }

        private PrimitiveMode PrimitiveMode {
            get;
            set;
        }

        private void Pentagon_Click(object sender, RoutedEventArgs e) {
            this.PrimitiveMode = PrimitiveMode.Triangle;
        }

        private void btnQuery_Click(object sender, RoutedEventArgs e) {

        }

        void CompositionTarget_Rendering(object sender, EventArgs e) {
            double xd = ((this.ActualWidth / 2) 
                - Mouse.GetPosition(mainViewport).X) * 0.02;
            double yd = ((this.ActualHeight / 2) 
                - Mouse.GetPosition(mainViewport).Y) * 0.02;

            PerspectiveCamera camera = (PerspectiveCamera)mainViewport.Camera;
            Point3D position = new Point3D(xd, yd, xd
                //Convert.ToDouble(cameraPositionZTextBox.Text)
            );
            Vector3D lookDirection = new Vector3D(2.5, 2.5, 0);
            //Vector3D lookDirection = new Vector3D(0, 0, -10);
                //Convert.ToDouble(lookAtZTextBox.Text)

            camera.Position = new Point3D(xd, -2.5, 7);
            camera.LookDirection = new Vector3D(
                lookDirection.X - camera.Position.X,
                lookDirection.Y - camera.Position.Y,
                lookDirection.Z - camera.Position.Z);
        }

        /// <summary>
        /// Creates a ModelVisual3D containing a text label.
        /// </summary>
        /// <param name="text">The string</param>
        /// <param name="textColor">The color of the text.</param>
        /// <param name="bDoubleSided">Visible from both sides?</param>
        /// <param name="height">Height of the characters</param>
        /// <param name="center">The center of the label</param>
        /// <param name="over">Horizontal direction of the label</param>
        /// <param name="up">Vertical direction of the label</param>
        /// <returns>Suitable for adding to your Viewport3D</returns>
        public static ModelVisual3D CreateTextLabel3D(
            string text,
            Brush textColor,
            bool bDoubleSided,
            double height,
            Point3D center,
            Vector3D over,
            Vector3D up) {
            // First we need a textblock containing the text of our label
            TextBlock tb = new TextBlock(new Run(text));
            tb.Foreground = textColor;
            tb.FontFamily = new FontFamily("Arial");

            // Now use that TextBlock as the brush for a material
            DiffuseMaterial mat = new DiffuseMaterial();
            mat.Brush = new VisualBrush(tb);

            // We just assume the characters are square
            double width = text.Length * height;

            // Since the parameter coming in was the center of the label,
            // we need to find the four corners
            // p0 is the lower left corner
            // p1 is the upper left
            // p2 is the lower right
            // p3 is the upper right
            Point3D p0 = center - width / 2 * over - height / 2 * up;
            Point3D p1 = p0 + up * 1 * height;
            Point3D p2 = p0 + over * width;
            Point3D p3 = p0 + up * 1 * height + over * width;

            // Now build the geometry for the sign.  It's just a
            // rectangle made of two triangles, on each side.

            MeshGeometry3D mg = new MeshGeometry3D();
            mg.Positions = new Point3DCollection();
            mg.Positions.Add(p0);    // 0
            mg.Positions.Add(p1);    // 1
            mg.Positions.Add(p2);    // 2
            mg.Positions.Add(p3);    // 3

            if (bDoubleSided) {
                mg.Positions.Add(p0);    // 4
                mg.Positions.Add(p1);    // 5
                mg.Positions.Add(p2);    // 6
                mg.Positions.Add(p3);    // 7
            }

            mg.TriangleIndices.Add(0);
            mg.TriangleIndices.Add(3);
            mg.TriangleIndices.Add(1);
            mg.TriangleIndices.Add(0);
            mg.TriangleIndices.Add(2);
            mg.TriangleIndices.Add(3);

            if (bDoubleSided) {
                mg.TriangleIndices.Add(4);
                mg.TriangleIndices.Add(5);
                mg.TriangleIndices.Add(7);
                mg.TriangleIndices.Add(4);
                mg.TriangleIndices.Add(7);
                mg.TriangleIndices.Add(6);
            }

            // These texture coordinates basically stretch the
            // TextBox brush to cover the full side of the label.

            mg.TextureCoordinates.Add(new Point(0, 1));
            mg.TextureCoordinates.Add(new Point(0, 0));
            mg.TextureCoordinates.Add(new Point(1, 1));
            mg.TextureCoordinates.Add(new Point(1, 0));

            if (bDoubleSided) {
                mg.TextureCoordinates.Add(new Point(1, 1));
                mg.TextureCoordinates.Add(new Point(1, 0));
                mg.TextureCoordinates.Add(new Point(0, 1));
                mg.TextureCoordinates.Add(new Point(0, 0));
            }

            // And that's all.  Return the result.

            ModelVisual3D mv3d = new ModelVisual3D();
            mv3d.Content = new GeometryModel3D(mg, mat); ;
            return mv3d;
        }

        protected List<IModelVisualizer> visualizers = new List<IModelVisualizer>();

        public void Visualize() {
            ClearViewport();
            SetCamera();

            IModelVisualizer visualizer = visualizers[this.cbModels.SelectedIndex];
            ModelVisual3D model = new ModelVisual3D();
            model.Content = visualizer.Draw(this.tbCriteria.Text);

            InteractiveCone cone = new InteractiveCone();
            this.mainViewport.Children.Add(model);
        }

        private Model3DGroup GetTetrahedron() {

            Model3DGroup group;

            double length = 5;

            string e = "(x0 - {0})^2+(x1-{1})^2-{2}^2";

            EquationGroup eg = new EquationGroup(new string[] {
                string.Format(e, length, length, length),
                string.Format(e, length, length, length)
            });

            double[] r = eg.Evaluate();

            Point3D p1 = new Point3D(0, 0, 0);
            Point3D p2 = new Point3D(0, 5, 0);
            Point3D p3 = new Point3D(r[0], r[1], 0);

            string x = "(x0 - {0})^2+(x1-{1})^2-(x3-{2})^2-{3}^2";

            EquationGroup egb = new EquationGroup(new string[]{
                string.Format(x, p1.X, p1.Y, p1.Z, length),
                string.Format(x, p2.X, p2.Y, p2.Z, length),
                string.Format(x, p3.X, p3.Y, p3.Z, length)
            });
            r = egb.Evaluate();

            Point3D t = new Point3D(r[0], r[1], r[2]);

            ModelVisual3D model = new ModelVisual3D();

            group = new Model3DGroup();
            group.Children.Add(CreateTriangleModel(
                p1, p2, p3));
            group.Children.Add(CreateTriangleModel(
                p1, p2, t));
            group.Children.Add(CreateTriangleModel(
                p2, p3, t));
            group.Children.Add(CreateTriangleModel(
                p3, p1, t));

            return group;
        }

        public void DrawCircle() {
        }

        public void DrawRectangle() {
        }

        public void DrawCube() {
        }

        private void slider1_DragLeave(object sender, DragEventArgs e) {
            MessageBox.Show("Hello");
        }

        private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
            Console.WriteLine("Hello");
        }

        private void XAxisSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {

        }

        private void YAxisSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {

        }

        private void ZAxisSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {

        }

        private void ZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {

        }
    }
}